using System;
using System.Collections.Generic;
using System.Linq;
using PropertyExpression.Common;

namespace PropertyExpression.ReactiveRelation
{
    public static partial class Value
    {
        public static IValue<TResult> OptionalResultFunc<T, TArgument, TResult>(
            this IValue<T> value, Func<IOptionalResultArgumentProvider, TArgument> argumentFunc,
            Func<T, TArgument, IOptionalValue<TResult>> func)
        {
            return new OptionalResultFuncValue<T, TArgument, TResult>(value, argumentFunc, func);
        }

        private class OptionalResultFuncValue<T, TArgument, TResult> : IValue<TResult>
        {
            private readonly IValue<T> value;
            private readonly Func<IOptionalResultArgumentProvider, TArgument> argumentFunc;
            private readonly Func<T, TArgument, IOptionalValue<TResult>> func;

            public OptionalResultFuncValue(IValue<T> value, 
                Func<IOptionalResultArgumentProvider, TArgument> argumentFunc,
                Func<T, TArgument, IOptionalValue<TResult>> func)
            {
                this.value = value;
                this.argumentFunc = argumentFunc;
                this.func = func;
            }

            public IEnumerable<ICell> GetCells()
            {
                return Execute(
                    (values, argument) =>
                    values.Select(valueContainer => valueContainer.Value)
                        .Concat(new[] {value}).SelectMany(currentValue => currentValue.GetCells())
                    );
            }

            public IOptionalValue<TResult> GetValue(IPropertySettor propertySettor, 
                IOneToManyCollectionNewItemProcessor oneToManyCollectionNewItemProcessor)
            {
                return Execute(
                    (values, argument) =>
                    value.GetValue(propertySettor, oneToManyCollectionNewItemProcessor)
                        .ProcessValue(result =>
                                     {
                                         foreach (var valueContainer in values)
                                         {
                                             valueContainer.InvokeGetValue(
                                                 propertySettor, oneToManyCollectionNewItemProcessor);
                                         }
                                         return func(result, argument);
                                     }
                        )
                    );
            }

            private TExecuteResult Execute<TExecuteResult>(
                Func<IEnumerable<IValueContainer>, TArgument, TExecuteResult> executeFunc)
            {
                var values = new LinkedList<IValueContainer>();
                return executeFunc(values, argumentFunc(new ArgumentProvider(values)));
            }

            private interface IValueContainer
            {
                IValue Value { get; }
                void InvokeGetValue(IPropertySettor propertySettor, 
                    IOneToManyCollectionNewItemProcessor oneToManyCollectionNewItemProcessor);
            }

            private class ArgumentProvider : IOptionalResultArgumentProvider
            {
                private readonly LinkedList<IValueContainer> values;

                public ArgumentProvider(LinkedList<IValueContainer> values)
                {
                    this.values = values;
                }

                public Func<IOptionalValue<TCurrentArgument>> _<TCurrentArgument>(
                    IValue<TCurrentArgument> value)
                {
                    var valueContainer = new ValueContainer<TCurrentArgument>(value);
                    values.AddLast(valueContainer);
                    return () => valueContainer.Result();
                }

                private class ValueContainer<TCurrentArgument> : IValueContainer
                {
                    private readonly IValue<TCurrentArgument> value;

                    public ValueContainer(IValue<TCurrentArgument> value)
                    {
                        this.value = value;
                    }

                    public Func<IOptionalValue<TCurrentArgument>> Result { get; private set; }

                    public void InvokeGetValue(IPropertySettor propertySettor, 
                        IOneToManyCollectionNewItemProcessor oneToManyCollectionNewItemProcessor)
                    {
                        Result = () => value.GetValue(propertySettor, oneToManyCollectionNewItemProcessor);
                    }

                    public IValue Value
                    {
                        get { return value; }
                    }
                }
            }
        }
    }
}
